/*
 * dnsserver.c
 * Authors: Doris, Stephanie
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include "serialize_deserialize_dns_names.h"
#include <time.h>

#define MAXBUFLEN 65535

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa) {
	if(sa->sa_family == AF_INET) {
		return &(((struct sockaddr_in*)sa)->sin_addr);
	}

	return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

struct node {
	char * name;
	int sock;
	struct node * next;
	struct node * prev;
	long ttl;
	char * value;	//i.e. - IP in class A
	time_t start;
};

int main(int argc, char * argv[]) {
	struct addrinfo hints, *servinfo, *p;
	int rv;
	int numbytes;
	struct sockaddr_storage remoteaddr;
	char buf[MAXBUFLEN];
	size_t addr_len;
	char remoteIP[INET6_ADDRSTRLEN];
	const char * server_port;
	int i;
	struct node * first;
	struct node * mapping;
	struct node * temp;

	fd_set master;
	fd_set read_fds;
	int fdmax;
	int sockfd;
	int newfd;

	if(argc == 2) {
		server_port = argv[1];
	} else {
		perror("Wrong number of arguments\n");
		exit(1);
	}

	FD_ZERO(&master);
	FD_ZERO(&read_fds);

	memset(&hints, 0, sizeof hints);
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_DGRAM;
	hints.ai_flags = AI_PASSIVE; //use my IP

	if((rv = getaddrinfo(NULL, server_port, &hints, &servinfo)) == -1) {
		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
		return 1;
	}

	for(p = servinfo; p != NULL; p = p->ai_next) {
		if((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
			perror("dnsserver: socket\n");
			continue;
		}

		if(bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
			close(sockfd);
			perror("dnsserver: bind\n");
			continue;
		}

		break;
	}

	if(p == NULL) {
		fprintf(stderr, "dnsserver: failed to bind socket\n");
		return 2;
	}

	freeaddrinfo(servinfo);

	FD_SET(sockfd, &master);	//add server's socket to master
	fdmax = sockfd;

	while(1) {
		read_fds = master;	//copy it
		if(select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) {
			perror("select");
			exit(4);
		}

		if(FD_ISSET(sockfd, &read_fds)) {	//something has come in
			if (recvfrom(sockfd, buf, MAXBUFLEN-1 , 0, (struct sockaddr *)&remoteaddr, &addr_len) > 0) {
				printf("Buffer: %s", buf);

				// parse DNS message
				printf("parsing\n");
				int i;
				//set id
				int id = 0;
				for (i = 0; i < 2; i++){
					id = id << (i*8);
					id = id || buf[i];
				}

				//set parameter
				int parameter = 0;
				for (i = 0; i < 2; i++){
					parameter = parameter << (i*8);
					parameter = parameter || buf[i+2];
				}

				//Parameter Bit 5
				if (((parameter&0x0200)>>10) == 1) { // authoritative/response message

				} else if (((parameter&0x0200)>>10) == 0) {

				} else {
					perror ("Unrecognized Parameter Bit 5\n");
				}

				//set numQ
				int numQ = 0;
				for (i = 0; i < 2; i++){
					numQ = numQ << (8*i);
					numQ = numQ || buf[i+4];
				}

				//set numAns
				int numAns = 0;
				for (i = 0; i < 2; i++){
					numAns = numAns << (8*i);
					numAns = numAns || buf[i+6];
				}

				//set numAuth
				int numAuth = 0;
				for (i = 0; i < 2; i++){
					numAuth = numAuth << (8*i);
					numAuth = numAuth || buf[i+8];
				}

				//set numAdd
				int numAdd = 0;
				for (i = 0; i < 2; i++){
					numAdd = numAdd << (8*i);
					numAdd = numAdd || buf[i+10];
				}

				//parsing Question
				short readSize;
				char *domainName;
				domainName = deserialize_DNS_name(buf+12, 512, &readSize);

				//parsing Answer
				int TTL = 0;
				for (i = 0; i < 2; i++){
					TTL = TTL << (8*i);
					TTL = TTL || buf[i+16+readSize+readSize+6];
				}

				int resourceDataL = 0; // ip address length in bytes
				for (i = 0; i < 2; i++){
					resourceDataL = resourceDataL << (8*i);
					resourceDataL = resourceDataL || buf[i+16+readSize+readSize+8];
				}

				char resData[resourceDataL]; // IP address
				for (i = 0; i < resourceDataL; i ++) {
					resData[i] = buf[i+16+readSize+readSize+10];
				}

				int found;
				// parameter Bit 0
				if (((parameter & 0x8000) >> 1) == 1) { // response message
					struct node * temp;
					found = 0;
					mapping = first;
					while(mapping != NULL) {
						if(mapping->name == domainName) {

							mapping->value = resData;
							mapping->ttl = TTL;
							found = 1;
						} else {
							temp = mapping;
							mapping = mapping->next;
						}
					}

					if(found == 0) {
						struct node * new;
						new = malloc(sizeof(struct node *));
						new->name = domainName;
						new->value =resData ;
						new->ttl = TTL;
						new->prev = temp;
						temp->next = new;
						new->start = time(NULL);
					}
				} else if (((parameter & 0x8000) >> 1) == 0) { // query message
					mapping = first;
					while(mapping != NULL) {
						if(mapping->name == domainName) {
							if(difftime(time(NULL), mapping->start) >= mapping->ttl) {
								//then it's expired
								//send message back with original query in QUESTION section
								//and nothing in ANSWER section
								//remove node from mapping
								mapping->prev->next = mapping->next;
								mapping->next->prev = mapping->prev;
								mapping->prev = NULL;
								mapping->next = NULL;
								temp = mapping;
								mapping = mapping->next;
								free(temp);
							} else {
								int c;
								int remttl = mapping->ttl - difftime(time(NULL), mapping->start);
								char tosend[strlen(buf) + strlen(domainName) + 14];
								for(c = 0; c < strlen(buf); c++) {
									tosend[c] = buf[c];
								}
								for(c = 0; c < strlen(domainName); c++) {
									tosend[strlen(buf)+c] = domainName[c];
								}
								char * ansdatalen[2];
								tosend[strlen(buf)+strlen(domainName)-1] =
									buf[12+strlen(domainName)-1];
								tosend[strlen(buf)+strlen(domainName)] =
									buf[12+strlen(domainName)];
								tosend[strlen(buf)+strlen(domainName)+1] =
									buf[12+strlen(domainName)+1];
								tosend[strlen(buf)+strlen(domainName)+2] =
									buf[12+strlen(domainName)+2];
								for(c = 0; c < 4; c++) {
									tosend[strlen(buf)+strlen(domainName)+2+c] =
										(char *) ((remttl>>(31-(8*c))) & 0x0f);
								}
								for(c = 0; c < 2; c++) {
									tosend[strlen(buf)+strlen(domainName)+6] =
										(char *) ((4>>(31-(8*c))) & 0x0f);
								}
								sendto(sockfd, tosend, strlen(tosend), 0, p->ai_addr, p->ai_addrlen);
							}
						}
					}
				} else {
					printf("Unrecognized Bit 0\n");
				}
			}
		} //end FD_ISSET loop
	} //end while loop
}// end main
